package com.example.demo.conf;

import com.example.demo.excpetion.MyOauthException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;

import java.util.Arrays;

/**
 * TODO
 *
 * @author funcas
 * @since 1.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;
    private final RedisConnectionFactory connectionFactory;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private UserDetailServiceImpl userDetailService;
//    private final DataSource dataSource;

    @Autowired
    public AuthConfig(AuthenticationManager authenticationManager, RedisConnectionFactory connectionFactory) {
        this.authenticationManager = authenticationManager;
        this.connectionFactory = connectionFactory;
    }


    @Bean
    public MyRedisTokenStore tokenStore() {
        MyRedisTokenStore store = new MyRedisTokenStore(connectionFactory);
        store.setSerializationStrategy(new ProtostuffSerial());
        return store;
    }

//    @Bean
//    public JdbcTokenStore tokenStore() {
//        return new JdbcTokenStore(dataSource);
//    }

//    @Bean
//    public TokenEnhancer tokenEnhancer() {
//        return new HmacTokenEnhancer();
//    }


    @Bean
    public MyTokenEnhancer tokenEnhancer() {
        return new MyTokenEnhancer();
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenServices(tokenServices(endpoints))
                .authenticationManager(authenticationManager)
                //若无，refresh_token会有UserDetailsService is required错误
                .userDetailsService(userDetailService)
                .tokenEnhancer(tokenEnhancer())
                .pathMapping("/oauth/token","/oauth/login")
//                .tokenServices()
                .tokenStore(tokenStore());
//                .exceptionTranslator(new CustomWebResponseExceptionTranslator());
        super.configure(endpoints);
    }

    private MyTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
        MyTokenServices tokenServices = new MyTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);//支持刷新token
        tokenServices.setReuseRefreshToken(true);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        addUserDetailsService(tokenServices, userDetailService);
        return tokenServices;
    }
    private void addUserDetailsService(MyTokenServices tokenServices, UserDetailsService userDetailsService) {
        if (userDetailsService != null) {
            PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
            provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(
                    userDetailsService));
            tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));
        }
    }

    /**
     * 使用非对称加密算法来对Token进行签名
     * @return
     */
//    @Bean
//    public JwtAccessTokenConverter jwtAccessTokenConverter() {
//
//        final JwtAccessTokenConverter converter = new JwtAccessToken();
//        //导入证书
//        KeyStoreKeyFactory keyStoreKeyFactory =
//                new KeyStoreKeyFactory(new ClassPathResource("jwt_key.jks"), "noitcnuf".toCharArray());
//        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("jwt_key"));
//
//        return converter;
//    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory().withClient("sys").secret(passwordEncoder.encode("123456"))
                .authorizedGrantTypes("authorization_code", "refresh_token", "client_credentials", "password")
                .accessTokenValiditySeconds(7200)
                .refreshTokenValiditySeconds(86400)
                .scopes("all");
    }


    private class CustomWebResponseExceptionTranslator extends DefaultWebResponseExceptionTranslator {
        @Override
        public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
            ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
            OAuth2Exception body = responseEntity.getBody();
            HttpHeaders headers = new HttpHeaders();
            headers.setAll(responseEntity.getHeaders().toSingleValueMap());
            // do something with header or response
            MyOauthException oauthException = new MyOauthException(body.getMessage());
            return new ResponseEntity<>(oauthException, headers, HttpStatus.OK);
        }
    }

}
